package collectors.people.controllers;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import collectors.people.models.PeopleUser;
import collectors.people.models.PeopleUser.FacebookData;
import collectors.people.callbacks.FacebookCallbacks.*;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;

import play.libs.F.Function;
import play.libs.F.Option;
import play.libs.F.Promise;
import play.libs.F;
import play.libs.Json;
import play.libs.WS;
import play.libs.WS.Response;
import play.mvc.Controller;
import play.mvc.Result;
import services.CollectorService;
import services.CollectorService.Services;
import collectors.people.services.FacebookQueryService;
import collectors.people.util.Config;

public class Application extends Controller {

	public static Result createUser() {
		PeopleUser user = PeopleUser.create();
		return ok(Json.toJson(user));
	}

	// TODO check if token is expired
	public static Result query(String query, Long id, int max) {
		PeopleUser user = PeopleUser.find.byId(id);

		if (user == null) {
			return notFound("Unknown id");
		} else {
			Promise<ArrayNode> users = FacebookQueryService.searchUserJSON(
					query, user.fbdata.accessToken);
			return async(users.map(new Function<ArrayNode, Result>() {
				@Override
				public Result apply(ArrayNode json) throws Throwable {
					return ok(json);
				}
			}));
		}
	}

	public static Result me(Long id) {
		PeopleUser user = PeopleUser.find.byId(id);
		if (user == null) {
			return notFound("Unknown id");
		} else {
			Promise<JsonNode> users = FacebookQueryService
					.getUserData(user.fbdata.accessToken);
			return async(users.map(new Function<JsonNode, Result>() {
				@Override
				public Result apply(JsonNode arg0) throws Throwable {
					return ok(arg0);
				}
			}));
		}
	}

	public static Result link(Long cid) {
		final PeopleUser user = PeopleUser.find.byId(cid);
		if (user == null) {
			return notFound("Unknown id");
		} else {
			try {
				String callback = collectors.people.controllers.routes.Application
						.linkCallback(F.None(), F.None(), F.None()).absoluteURL(
								request()) + "?dummy=true";
				return redirect(Config.get().fblogin
						.replace("$STATE$", cid + "$$")
						.replace("$REDIRECT$", URLEncoder.encode(callback, "UTF-8")));	
			} catch (RuntimeException e) {
				e.printStackTrace();
				return internalServerError(e.getMessage());
			} catch (UnsupportedEncodingException e) {
				return internalServerError(e.getMessage());
			}
		}
	}
	
	public static Result linkCallback(Option<String> code, Option<String> state, Option<String> error) {
		//FIXME I have no fucking idea why play is swapping the contents of error and state.
		if (error.isDefined() && error.get().contains("$$")) {
			error = F.None();
			state = F.Some(error.get());
		} 
		
		if (!code.isDefined()) {
			return badRequest("facebook code is missing");
		} else if (!state.isDefined()) {
			return badRequest("state parameter missing");
		} else {
			if (error.isDefined() && error.get().equals("access_denied")) {
				return unauthorized("You must grant the required permissions!");
			} else {
				String[] stateInfo = state.get().split("\\$\\$");
				final long id = Long.parseLong(stateInfo[0]);
				final String redirectUrl;
				if (stateInfo.length > 1) {
					redirectUrl = stateInfo[1]; 
				} else {
					redirectUrl = "";
				}
				final PeopleUser user = PeopleUser.find.byId(id);		
				Config config = Config.get();
				Promise<WS.Response> token = getOAuthToken(code, config);
				return async(
						token.flatMap(new GetInfo()).map(
						new Function<FacebookData, Result>() {
							@Override
							public Result apply(FacebookData data)
									throws Throwable {
								user.updateFacebookData(data);
								return redirect(controllers.routes.Peopleyou.linkCallback(id, true));
							}
						}));
			}
		}
	}

	public static Promise<Response> getOAuthToken(Option<String> code,
			Config config) {
		return WS.url(config.fboauth)
				.setQueryParameter("client_id", config.fbkey)
				.setQueryParameter("client_secret", config.fbsecret)
				.setQueryParameter(
						"redirect_uri",
						collectors.people.controllers.routes.Application
								.linkCallback(F.None(), F.None(), F.None())
								.absoluteURL(request()) + "?dummy=true")
				.setQueryParameter("code", code.get()).get();
	}

	public static Result unlink(Long cid) {
		boolean ok = cleanUserData(cid);
		if (ok) {
			return ok("Link data cleared! Remember to remove the permissions you granted"
					+ " to this app on facebook! You will need to login again to use this app.");
		} else {
			return notFound("Unknown id");
		}
	}
	
	public static boolean cleanUserData(Long cid) {
		PeopleUser user = PeopleUser.find.byId(cid);
		boolean userExists = false;
		if (user != null) {
			userExists = true;
			user.clearLinkData();
		}
		return userExists;
	}
}
